#pragma once
/*******************************************************************************************************************************
 * 此模块的功能为OJ控制策略
 * Control:提供网页的构成，编译和运行的请求的方法
 * Machine:提供负载均衡的主机:，配置主机的ip，端口号，负载。提供更新负载，减少负载，清空负载功能
 * LoadBlance:负载均衡器，根据配置文件加载主机的属性到Machine中。提供均衡选择（轮询检测模式），上线，离线主机的服务
 ******************************************************************************************************************************/
#include "../Comm/comm.hpp"
#include "../Comm/log.hpp"
#include "../Comm/util.hpp"
#include "oj_model.hpp"
#include "oj_view.hpp"
namespace ns_control
{
    using namespace std;
    using namespace ns_log;
    using namespace ns_util;
    using namespace ns_model;
    using namespace ns_view;
    using namespace httplib;

    // 部署业务的主机属性描述
    class Machine
    {
    public:
        std::string _ip;  // 布置任务的主机地址
        int _port;        // 编译服务端口号
        uint64_t _load;   // 编译服务负载
        std::mutex *_mtx; // 限定访问安全的锁，C++中mutex不允许拷贝，使用指针，拷贝地址
    public:
        Machine()
            : _ip(""), _port(0), _load(0), _mtx(nullptr)
        {
        }
        ~Machine()
        {
            // delete _mtx;
        }

    public:
        // 此函数为增加负载
        void IncLoad()
        {
            if (_mtx)
                _mtx->lock();
            ++_load;
            if (_mtx)
                _mtx->unlock();
        }
        // 此函数为减少负载
        void DecLoad()
        {
            if (_mtx)
                _mtx->lock();
            --_load;
            if (_mtx)
                _mtx->unlock();
        }
        // 此函数为获取负载
        uint64_t GetLoad()
        {
            uint64_t load = 0;
            if (_mtx)
                _mtx->lock();
            load = _load;
            if (_mtx)
                _mtx->unlock();
            return load;
        }
        // 此函数为清空负载
        void ResetLoad()
        {
            if (_mtx)
                _mtx->lock();
            _load = 0;
            if (_mtx)
                _mtx->unlock();
        }
    };
    // 负载均衡模块
    const std::string server_machine = "./conf/service_machine.conf";
    class LoadBlance
    {
    private:
        // 这里存放着给我们提供服务的所有主机
        std::vector<Machine> _machines;
        // 存放所有在线主机
        std::vector<int> _online; //_machines中下标即代表主机id
        // 存放所有离线主机
        std::vector<int> _offline;
        // 保证加载服务的安全型性
        std::mutex _mtx;

    public:
        LoadBlance()
        {
            // 构造时加载配置文件
            assert(LoadConf(server_machine));
            LOG(NORMAL) << "加载" << server_machine << "成功" << endl;
        }
        ~LoadBlance()
        {
        }

    public:
        // 此函数功能为加载提供服务的机器的配置文件./conf/service_machine.conf
        bool LoadConf(const std::string &machine_conf) // 参数为配置文件名称
        {
            // 打开文件
            std::ifstream in(machine_conf);
            if (!in.is_open())
            {
                LOG(FATAL) << "加载配置文件: " << machine_conf << " 失败" << endl;
                return false;
            }
            // 读取一行内容按规则切分
            std::string line;
            while (std::getline(in, line))
            {
                std::vector<std::string> tokens;
                StringUtil::SplitString(line, tokens, ":");
                if (tokens.size() != 2)
                {
                    LOG(WARNING) << "切分: " << line << " 失败" << endl;
                    continue;
                }
                // 将切分的内容填充到Machine中
                Machine m;
                m._ip = tokens[0];
                m._port = atoi(tokens[1].c_str());
                m._load = 0; // 刚加载配置文件，负载为0
                m._mtx = new std::mutex();

                _online.push_back(_machines.size());
                _machines.push_back(m);
            }
            in.close();
            // cout<<"load正常"<<endl;
            return true;
        }
        // 此函数功能为选择最小负载的机器提供服务
        // 参数：id,输出型参数,将机器的id返回出去. m,输出型参数，返回指向服务机器配置的指针
        bool MinLoadChoice(int *id, Machine **m)
        {
            // cout << "MinLoad" << endl;
            //  1.选择好的主机，并更新机器的负载
            //  2.有可能需要离线主机
            //  选择主机时要确保当前的访问为安全的
            _mtx.lock();
            int online_num = _online.size(); // 确定存活主机数量
            if (online_num == 0)
            {
                // 没有主机存活进行提供服务，致命错误
                _mtx.unlock();
                LOG(FATAL) << "所有的后端编译服务的主机均已离线，致命错误，尽快维护" << endl;
                return false;
            }
            // 均衡选择方式：轮询
            // 先将0号主机设定为最小负载
            *id = _online[0];
            *m = &_machines[_online[0]];

            uint64_t min_load = _machines[_online[0]].GetLoad();
            // 轮询选择最小负载主机
            for (int i = 1; i < online_num; i++)
            {
                uint64_t cur_load = _machines[_online[i]].GetLoad();
                if (cur_load < min_load)
                {
                    min_load = cur_load;
                    *id = _online[i];
                    *m = &_machines[_online[i]];
                }
            }
            _mtx.unlock();
            // cout<<"min正常"<<endl;
            return true;
        }
        // 此函数为离线对应主机功能
        void OfflineMachine(int id)
        {
            _mtx.lock();
            for (auto iter = _online.begin(); iter != _online.end(); iter++)
            {
                if (*iter == id)
                {
                    _machines[id].ResetLoad();
                    _online.erase(iter);
                    _offline.push_back(id);

                    break;
                }
            }
            _mtx.unlock();
        }
        // 此函数为上线主机功能
        void OnlineMachine()
        {
            _mtx.lock();
            _online.insert(_online.end(), _offline.begin(), _offline.end());
            _offline.erase(_offline.begin(), _offline.end());
            _mtx.unlock();
            LOG(NORMAL) << "所有的主机已经上线" << endl;
        }
        // 此函数为显示在线与离线主机--->仅在调试功能中使用
        void ShowMachine()
        {
            _mtx.lock();
            std::cout << "当前在线主机: ";
            for (auto &id : _online)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;
            std::cout << "当前离线主机: ";
            for (auto &id : _offline)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;
            _mtx.unlock();
        }
    };
    // 核心业务逻辑控制器
    class Control
    {
    private:
        Model _model;            // 提供后台数据
        View _view;              // 提供网页渲染
        LoadBlance _load_blacne; // 提供负载均衡器
    public:
        Control() {}
        ~Control() {}
        // 此函数为通过ctrl+\完成重现上线功能的配置加载
        void RecoveryMachine()
        {
            _load_blacne.OnlineMachine();
        }
        // 此函数为将所有题目构建成网页
        bool AllQuestions(std::string *html)
        {
            bool ret = true;
            std::vector<Question> all;
            if (_model.GetAllQuestion(&all))
            {
                // 获取题目信息成功
                // 利用题目序号进行排序
                 sort(all.begin(), all.end(), [](const struct Question &q1, const struct Question &q2){
                    return atoi(q1._number.c_str()) < atoi(q2._number.c_str());
                });
                // 调用渲染
                _view.AllExpandHtml(all, html);
            }
            else
            {
                *html = "获取题目失败，未能形成题目列表";
                // _view.ErrExpandHtml(html);
                ret = false;
            }
            return ret;
        }

        // 此函数为将指定的一个题目内容构建网页
        bool OneQuestion(const std::string &number, std::string *html)
        {
            bool ret = true;
            struct Question q;
            if (_model.GetOneQuestion(number, &q))
            {
                // 获取题目成功
                // 进行渲染
                _view.OneExpandHtml(q, html);
            }
            else
            {
                *html = "题目编号：" + number + " 该题目不存在！";
                // _view.ErrExpandHtml(html);
                ret = false;
            }
            return ret;
        }
        // 此函数功能为将用户提交的代码进行编译和运行，并将结果带出
        // 参数：
        // number:表示题目编号
        // in_json:输入型参数，用户提交的代码和输入
        // out_json:输出型参数，用户代码的编译运行结果
        void Judge(const std::string &number, const std::string in_json, std::string *out_json)
        {
            // 1.根据题目编号，获取对应题目的详细信息
            struct Question q;
            _model.GetOneQuestion(number, &q);
            // 2.in_json进行序列化，拿到用户的code，input
            Json::Reader reader;
            Json::Value in_value;
            reader.parse(in_json, in_value); // 反序列化
            // 拿到用户的code
            std::string code = in_value["code"].asString();
            // 3.拼接用户code和题目测试用例的主函数，形成新的代码，并进行序列化
            Json::Value compile_value; // 序列化
            compile_value["input"] = in_value["input"].asString();
            compile_value["code"] = code + "\n" + q._tail;
            // cout<<q._tail<<endl;
            compile_value["cpu_limit"] = q._cpu_limit;
            compile_value["mem_limit"] = q._mem_limit;
            Json::FastWriter writer;
            std::string compile_string = writer.write(compile_value); // 序列化
            // 4.找到负载最小的主机，进行编译与运行服务
            while (true)
            {
                int id = 0;
                Machine *m = nullptr;
                if (!_load_blacne.MinLoadChoice(&id, &m))
                {
                    // 没有找到机器
                    break;
                }
                // 5.找到机器，发起http请求，得到结果
                Client cli(m->_ip, m->_port); // 创建客户端
                m->IncLoad();                 // 负载自增一下
                LOG(NORMAL) << " 选择主机成功, 主机id: " << id << " 详情: " << m->_ip << ":" << m->_port << " 当前主机的负载是: " << m->GetLoad() << endl;
                if (auto res = cli.Post("/cmp_and_run", compile_string, "application/json;charset=utf-8"))
                {
                    // 6.将结果赋值给out_json
                    if (res->status == 200) // 请求成功
                    {

                        *out_json = res->body;
                        m->DecLoad();
                        LOG(NORMAL) << "请求编译运行服务成功" << endl;
                        break;
                    }
                    m->DecLoad();
                }
                else
                {
                    // 请求失败
                    LOG(ERROR) << " 当前请求主机id: " << id << " 详情: " << m->_ip << ":" << m->_port << "可能已离线" << endl;
                    _load_blacne.OfflineMachine(id);
                    _load_blacne.ShowMachine();
                }
            }
        }
    };
}
